/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.engine.runtime.page;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.contexts.WorkflowContext;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.Record;
import cn.easyplatform.engine.runtime.PageTaskSupport;
import cn.easyplatform.entities.BaseEntity;
import cn.easyplatform.entities.EntityInfo;
import cn.easyplatform.entities.beans.page.PageBean;
import cn.easyplatform.entities.beans.table.TableBean;
import cn.easyplatform.entities.helper.EventLogic;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.response.PageResponseMessage;
import cn.easyplatform.messages.vos.PageVo;
import cn.easyplatform.support.scripting.BreakPoint;
import cn.easyplatform.type.*;
import cn.easyplatform.util.EntityUtils;
import cn.easyplatform.util.MessageUtils;
import cn.easyplatform.util.RuntimeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class PageTask implements PageTaskSupport {

    private final static Logger log = LoggerFactory.getLogger(PageTask.class);

    @Override
    public IResponseMessage<?> doTask(CommandContext cc, BaseEntity bean) {
        try {
            WorkflowContext ctx = cc.getWorkflowContext();
            PageBean pb = EntityUtils.getInheritPage(cc, ctx.getRecord(), (PageBean) bean);
            if (!Strings.isBlank(pb.getTable())) {
                String tableId = pb.getTable().trim();
                if (tableId.charAt(0) == '$') {
                    tableId = (String) ctx.getRecord().getValue(
                            tableId.substring(1));
                    pb = pb.clone();
                    pb.setTable(tableId);
                }
            }
            RuntimeUtils.initPage(cc, ctx, pb);
            TableBean tablebean = null;
            if (ctx.getRecord().getData() == null) {
                if (ctx.isAutoMapping() || Strings.isBlank(pb.getTable())) {
                    ctx.autoMapping(cc);
                } else {// 先初始化,为下面的TASK_INIT执行映射作准备
                    tablebean = cc.getEntity(pb.getTable());
                    if (tablebean == null)
                        return MessageUtils.entityNotFound(
                                EntityType.TABLE.getName(), pb.getTable());
                    if (tablebean.getKey() != null
                            && !tablebean.getKey().isEmpty()) {
                        Object[] keys = new Object[tablebean.getKey().size()];
                        int index = 0;
                        for (String name : tablebean.getKey()) {
                            FieldDo field = ctx.getRecord().getFieldQuietly(
                                    name);
                            if (field == null) {
                                keys = null;
                                break;
                            }
                            keys[index++] = field.getValue();
                        }
                        if (keys != null) {
                            for (String name : tablebean.getKey())
                                ctx.getRecord().removeVariable(name);
                            cc.getSync().lock(tablebean.getId(), keys);
                            ctx.setData(RuntimeUtils.getTableRecord(cc,
                                    tablebean, keys));
                        }
                    }
                    if (ctx.getData() == null) {
                        boolean genKey = false;
                        if (tablebean.isAutoKey()
                                && ctx.getParameterAsChar("814") == 'C')
                            genKey = true;
                        ctx.setData(RuntimeUtils.createRecord(cc, tablebean,
                                genKey));
                    }
                }
            }
            EventLogic el = RuntimeUtils.castTo(cc, ctx.getRecord(),
                    ctx.getParameter("818"));
            if (el != null) {
                // 如果有上一个或者是从已有的触发的功能，在这里可以实行变量的声明以及mapping的处理
                // $xxx=@xxx
                // $表示当前的栏位或变量，@表示上一个功能的变量或栏位
                //
                ctx.getRecord().setParameter("808", Constants.ON_INIT);
                RecordContext[] rcs = ctx.getMappingRecord();
                String result = RuntimeUtils.eval(cc, el, rcs);
                if (!result.equals("0000"))
                    return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                            ctx.getId(), result);
            }
            if (ctx.isAutoMapping()) {
                char code = ctx.getRecord().getParameterAsChar("814");
                if (code == 'U' || code == 'D') {
                    TableBean tb = cc.getEntity(ctx.getRecord()
                            .getParameterAsString("833"));
                    cc.getSync().lock(tb, ctx.getRecord());
                }
            }
            String table = ctx.getParameterAsString("833");
            if (ctx.getRecord().getData() == null && !Strings.isBlank(table)) {
                char code = ctx.getParameterAsChar("814");
                if (tablebean == null)
                    tablebean = cc.getEntity(table);
                if (tablebean == null)
                    return MessageUtils.entityNotFound(
                            EntityType.TABLE.getName(), table);
                if (code != 'C' && !ctx.isAutoMapping()) {
                    // 通过映射后主KEY名称来获取记录
                    RecordContext rc = ctx.getRecord();
                    Record data = RuntimeUtils
                            .getTableRecord(cc, tablebean, rc);
                    rc.setData(data);
                }
            }
            // 页面的加载逻辑
            el = RuntimeUtils.castTo(cc, ctx.getRecord(),
                    ctx.getParameter("835"));
            if (el != null) {
                ctx.getRecord().setParameter("808", Constants.ON_LOAD);
                String result = RuntimeUtils.eval(cc, el,
                        ctx.getMappingRecord());
                if (!result.equals("0000"))
                    return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                            ctx.getId(), result);
            }
            return createPage(cc, pb);
        } catch (Exception ex) {
            return MessageUtils.getMessage(ex, log);
        }
    }

    @Override
    public IResponseMessage<?> doPageBreakPoint(CommandContext cc, BreakPoint bp) {
        try {
            WorkflowContext ctx = cc.getWorkflowContext();
            EventLogic el = (EventLogic) ctx.getParameter("818");
            if (el != null && bp.isInstance(el)) {
                String result = RuntimeUtils.eval(cc, bp);
                if (!result.equals("0000"))
                    return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                            ctx.getId(), result);
            }
            // 页面的加载逻辑
            el = (EventLogic) ctx.getParameter("835");
            if (el != null && bp.isInstance(el)) {
                String result = RuntimeUtils.eval(cc, bp);
                if (!result.equals("0000"))
                    return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                            ctx.getId(), result);
            }
            PageBean pb = cc.getEntity(ctx.getParameterAsString("831"));
            return createPage(cc, pb);
        } catch (Exception ex) {
            return MessageUtils.getMessage(ex, log);
        }
    }

    @Override
    public IResponseMessage<?> doNext(CommandContext cc,
                                      Map<String, Object> data) {
        WorkflowContext ctx = cc.getWorkflowContext();
        EventLogic el = RuntimeUtils.castTo(cc, ctx.getRecord(),
                ctx.getParameter("836"));
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            FieldDo fd = ctx.getRecord().getField(entry.getKey());
            fd.setValue(RuntimeUtils.castTo(fd, entry.getValue()));
        }
        if (el != null) {
            ctx.getRecord().setParameter("808", Constants.ON_REFRESH);
            String result = RuntimeUtils.eval(cc, el, ctx.getRecord());
            if (!result.equals("0000"))
                return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                        ctx.getId(), result);
        }
        ctx.onNext(cc);
        el = RuntimeUtils.castTo(cc, ctx.getRecord(), ctx.getParameter("837"));
        if (el != null) {
            ctx.getRecord().setParameter("808", Constants.ON_OK);
            String result = RuntimeUtils.eval(cc, el, ctx.getRecord());
            if (!result.equals("0000"))
                return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                        ctx.getId(), result);
        }
        return null;
    }

    @Override
    public IResponseMessage<?> doNextBreakPoint(CommandContext cc, BreakPoint bp) {
        WorkflowContext ctx = cc.getWorkflowContext();
        EventLogic el = RuntimeUtils.castTo(cc, ctx.getRecord(),
                ctx.getParameter("836"));
        if (el != null && bp.isInstance(el)) {
            String result = RuntimeUtils.eval(cc, bp);
            if (!result.equals("0000"))
                return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                        ctx.getId(), result);
            bp = null;
        }
        if (bp == null)
            ctx.onNext(cc);
        el = RuntimeUtils.castTo(cc, ctx.getRecord(), ctx.getParameter("837"));
        if (el != null) {
            if (bp != null && bp.isInstance(el)) {
                String result = RuntimeUtils.eval(cc, bp);
                if (!result.equals("0000"))
                    return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                            ctx.getId(), result);
            } else {
                String result = RuntimeUtils.eval(cc, el, ctx.getRecord());
                if (!result.equals("0000"))
                    return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                            ctx.getId(), result);
            }
        }
        return null;
    }

    @Override
    public IResponseMessage<?> doPrev(CommandContext cc) {
        WorkflowContext ctx = cc.getWorkflowContext();
        if (ctx.getParameterAsBoolean("854")) {
            ctx.setParameter("854", false);
            TableBean tablebean = cc.getEntity(ctx.getParameterAsString("833"));
            if (tablebean == null)
                return MessageUtils.entityNotFound(EntityType.TABLE.getName(),
                        ctx.getParameterAsString("833"));
            ctx.setData(RuntimeUtils.createRecord(cc, tablebean, false));
        }
        Object src = ctx.getParameter("838");
        if (src != null) {
            ctx.getRecord().setParameter("808", Constants.ON_BACK);
            String result = RuntimeUtils.eval(cc, src, ctx.getRecord());
            if (!result.equals("0000"))
                return MessageUtils.byErrorCode(cc, ctx.getRecord(),
                        ctx.getId(), result);
        }
        PageBean pb = cc.getEntity(ctx.getParameterAsString("831"));
        try {
            return createPage(cc, pb);
        } catch (Exception ex) {
            return MessageUtils.getMessage(ex, log);
        }
    }

    private IResponseMessage<?> createPage(CommandContext cc, PageBean pb) {
        WorkflowContext ctx = cc.getWorkflowContext();
        List<FieldVo> fields = new ArrayList<FieldVo>();
        PageVo pv = new PageVo();
        pv.setId(ctx.getId());
        String title = ctx.getParameterAsString("811");
        if (!Strings.isBlank(title)) {
            if (title.charAt(0) == '$')
                title = (String) ctx.getRecord().getValue(title.substring(1));
        } else
            title = "";
        pv.setTitile(title);
        pv.setHeight(pb.getHeight());
        pv.setWidth(pb.getWidth());
        if (pb.getDestination() != null && pb.getDestination().startsWith("$"))
            pv.setDestination((String) ctx.getRecord().getValue(pb.getDestination().trim().substring(1)));
        else
            pv.setDestination(pb.getDestination());
        pv.setOnMessage(pb.getOnMessage());
        pv.setImage(ctx.getParameterAsString("813"));
        pv.setOpenModel(ctx.getParameterAsInt("805"));
        pv.setProcessCode(ctx.getParameterAsString("814"));
        pv.setOnVisible(pb.getOnVisible());
        pv.setOnShow(pb.getOnShow());
        pv.setScript(pb.getScript());
        pv.setVisible(ctx.getParameterAsBoolean("816"));
        DeviceType deviceType = cc.getUser().getDeviceType();
        StringBuilder sb = new StringBuilder();
        if (deviceType == DeviceType.AJAX || Strings.isBlank(pb.getMil())) {
            if (!Strings.isBlank(pb.getAjax())) {
                if (pb.getAjaxDevice() != null) {
                    pv.setStyle(pb.getAjaxDevice().getStyle());
                    if (!Strings.isBlank(pb.getAjaxDevice().getJavascript()))
                        pv.setJavascript(parseScript(ctx.getRecord(), pb.getAjaxDevice().getJavascript()));
                    if (pb.getAjaxDevice().getBindVariables() != null)
                        RuntimeUtils.bindVariables(cc, ctx, pb.getAjaxDevice().getBindVariables(), pv);
                }
                if (Strings.isBlank(pb.getApply()))
                    parseXml(cc, ctx.getRecord(), fields, sb, pb.getAjax());
                else {
                    EntityInfo e = cc.getEntity(pb.getApply());
                    if (e != null)
                        pv.setApply(e.getContent());
                    sb.append(pb.getAjax());
                }
            } else
                sb.append("<div/>");
        } else if (!Strings.isBlank(pb.getMil())) {
            if (pb.getMilDevice() != null) {
                pv.setStyle(pb.getMilDevice().getStyle());
                if (!Strings.isBlank(pb.getMilDevice().getJavascript()))
                    pv.setJavascript(parseScript(ctx.getRecord(), pb.getMilDevice().getJavascript()));
                if (pb.getMilDevice().getBindVariables() != null)
                    RuntimeUtils.bindVariables(cc, ctx, pb.getMilDevice().getBindVariables(), pv);
            }
            if (Strings.isBlank(pb.getApply()))
                parseXml(cc, ctx.getRecord(), fields, sb, pb.getMil());
            else {
                EntityInfo e = cc.getEntity(pb.getApply());
                if (e != null)
                    pv.setApply(e.getContent());
                sb.append(pb.getMil());
            }
        } else
            sb.append("<div/>");
        pv.setPage(sb.toString());
        sb = null;
        pv.setFields(fields);
        return new PageResponseMessage(pv);
    }

    private String parseScript(RecordContext rc, String script) {
        StringBuilder sb = new StringBuilder();
        char[] cs = script.toCharArray();
        int len = cs.length;
        for (int i = 0; i < len - 1; i++) {
            if (cs[i] == '/' && cs[i + 1] == '/') {// 处理注解符号//
                while (i < len) {
                    sb.append(cs[i]);
                    if (cs[i] == '\n') {
                        break;
                    }
                    i++;
                }
            } else if (cs[i] == '/' && cs[i + 1] == '*') {// 处理注解符号/*和*/
                while (i < len) {
                    sb.append(cs[i]);
                    if (cs[i] == '*' && i < len - 1 && cs[i + 1] == '/') {
                        break;
                    }
                    i++;
                }
            } else if (cs[i] == '#' && cs[i + 1] == '{') {
                StringBuilder tmp = new StringBuilder();
                i += 2;
                while (i < len) {
                    if (cs[i] == '}')
                        break;
                    tmp.append(cs[i]);
                    i++;
                }
                String name = tmp.toString();
                if (!Strings.isBlank(name)) {
                    FieldDo field = rc.getField(name);
                    sb.append(field.getValue());
                }
            } else
                sb.append(cs[i]);
        }
        sb.append(cs[len - 1]);
        return sb.toString();
    }

    private void parseXml(CommandContext cc, RecordContext rc,
                          List<FieldVo> fields, StringBuilder sb, String xml) {
        char[] cs = xml.toCharArray();
        int len = cs.length;
        Map<String, String> resources = null;
        StringBuilder tmp = new StringBuilder();
        for (int i = 0; i < len; i++) {
            if (cs[i] == '<' && i < len - 4 && cs[i + 1] == '!'
                    && cs[i + 2] == '-' && cs[i + 3] == '-') {// <!-- .... -->
                i += 4;
                while (i < len) {
                    if (cs[i] == '-' && i < len - 3 && cs[i + 1] == '-'
                            && cs[i + 2] == '>') {
                        i += 3;
                        break;
                    }
                    i++;
                }
            }
            if (i < len) {
                if (cs[i] == '<' && i < len - 8 && cs[i + 1] == 'i'
                        && cs[i + 2] == 'n' && cs[i + 3] == 'c' && cs[i + 4] == 'l'
                        && cs[i + 5] == 'u' && cs[i + 6] == 'd' && cs[i + 7] == 'e') {// <include>
                    i += 8;
                    tmp.setLength(0);
                    while (i < len) {
                        /* <include src="#{sss}" /> */
                        if (cs[i] == '/' && cs[i + 1] == '>') {
                            i++;
                            break;
                            /* <include src="#{sss}" ></include> */
                        } else if (cs[i] == '<' && cs[i + 1] == '/') {
                            i += 9;
                            break;
                        }
                        tmp.append(cs[i]);
                        i++;
                    }
                    String str = tmp.toString().trim();
                    /* <include src="#{sss}" ></include> */
                    if (str.indexOf(">") > 0)
                        str = StringUtils.substringBetween(str, "=", ">").trim();
                        /* <include src="#{sss}" /> */
                    else
                        str = StringUtils.substringAfter(str, "=").trim();
                    str = StringUtils.substringBetween(str, "\"", "\"");
                    if (str.startsWith("#{") && str.endsWith("}")) {
                        str = StringUtils.substringBetween(str, "#{", "}");
                        str = (String) rc.getValue(str);
                    }
                    if (!Strings.isBlank(str)) {
                        PageBean pb = cc.getEntity(str);
                        if (pb != null) {
                            str = pb.getAjax().trim();
                            if (str.startsWith("<?xml"))
                                str = StringUtils.substringAfterLast(str, "?>");
                            parseXml(cc, rc, fields, sb, str);
                        }
                    }
                } else if (cs[i] == '#' && i < len - 1 && cs[i + 1] == '{') {
                    tmp.setLength(0);
                    int j = i - 1;
                    i += 2;
                    while (i < len) {
                        if (cs[i] == '}')
                            break;
                        else
                            tmp.append(cs[i]);
                        i++;
                    }
                    String name = tmp.toString();
                    if (name.equals("")) {
                        throw new EasyPlatformWithLabelKeyException("page.bind.field.error");
                    } else {
                        tmp.setLength(0);
                        byte mark = -1;
                        for (; j > 0; j--) {
                            if (cs[j] == '"') {
                                while (true) {
                                    if (cs[--j] != ' ')
                                        break;
                                }
                                mark = 0;
                            }
                            if (mark == 0 && cs[j] == '=') {
                                while (true) {
                                    if (cs[--j] != ' ')
                                        break;
                                }
                                mark = 1;
                            }
                            if (mark == 1) {
                                tmp.append(cs[j]);
                                if (cs[j] == ' ')
                                    break;
                            }
                        }
                        String attribute = tmp.reverse().toString().trim();
                        if (attribute.equals("id")) {
                            FieldDo field = rc.getField(name);
                            FieldVo fv = RuntimeUtils.castTo(field, name);
                            if (!Strings.isBlank(field.getAcc())) {
                                Object val = rc.getValue(field.getAcc());
                                if (val != null) {
                                    if (val instanceof Number)
                                        fv.setDecimal(((Number) val).intValue());
                                    else {
                                        Integer decimal = cc.getAcc(val.toString());
                                        fv.setDecimal(decimal == null ? 0 : decimal);
                                    }
                                }
                            }
                            fields.add(fv);
                            sb.append("#").append(name);
                        } else if (name.startsWith("'")) {// i18n#{'xxx'}
                            String code = name.substring(1, name.length() - 1);
                            if (code.startsWith("$"))
                                code = (String) rc.getValue(code.substring(1));
                            if (resources == null)
                                resources = cc.getLabels();
                            sb.append(resources.get(code));
                        } else {
                            Object o = rc.getValue(name);
                            if (o != null) {
                        /*(if (o instanceof String) {
                            String val = o.toString();
                            if (val.startsWith("$")) {
                                if (val.startsWith("$7")) {
                                    String n = val.substring(0, 4);
                                    sb.append(val.replace(n,
                                            (String) rc.getValue(n.substring(1))));
                                } else
                                    sb.append(rc.getValue(val.substring(1)));
                            } else
                                sb.append(val);
                            //parseXml(cc, rc, fields, sb, (String) o);
                        } else*/
                                sb.append(o);
                            }
                        }
                    }
                } else
                    sb.append(cs[i]);
            }
        }
        tmp = null;
        cs = null;
    }
}
